﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using XFControls.XFPanels.XFPanelModals;

namespace XFControls.XFPanels
{
    [Flags]
    public enum PanelAttributes
    {
        None = 0x0,
        ScrollPast = 0x1,
        Swipe = 0x2
    }
    public enum States
    {
        Idle,
        Busy,
        Click,
        AlphaPanel,
        UpdateClick,
        Scroll,
        KineticScroll,
        SlideLeft,
        SlideRight,
        Modal,
        InsertText
    }
    public enum XFModal
    {
        Full,
        Bottom
    }
    public class TextInputEventArgs : EventArgs
    {
        public string TextEntered { get; set; }

        public TextInputEventArgs(string text)
        {
            TextEntered = text;
        }
    }

    public partial class XFPanelBase : Panel, IDisposable
    {
        public event TextInputEventHandler TextEntered;
        public delegate void TextInputEventHandler(object sender, TextInputEventArgs e);
        public XFPanelBase LeftPanel { get; protected set; }
        public XFPanelBase RightPanel { get; protected set; }
        public bool ScrollbarVisible { get; private set; }
        public Color ScrollbarColor { get; set; }
        public bool AlphaBarVisible { get; private set; }
        public int LocationY { get; protected set; }
        public int LocationX
        {
            get
            {
                return Location.X;
            }
            protected set
            {
                Location = new Point(value, 0);
            }
        }
        public int AlphaBarWidth
        {
            get
            {
                if (_alphabar == null)
                    return 0;
                return _alphabar.Width;
            }
        }
        public States State { get { return _curState; } }
        public PanelAttributes Attributes { get; set; }
        public bool SavePanelAfterSlide { get; set; }
        public string PanelName { get; set; }

        private int offset;
        private int scrLast;
        private int tolerance = 25;
        private int maxVelocity = 45;
        private int scrVelocity;
        private Point mouseDown;
        private double time;
        private int slideX;
        private int visibleHeight;
        private IXFModal _modal;
        private int notHeight = 50;
        private bool _updateSetup;

        protected States _curState;
        protected Bitmap _offBitmap;
        protected Bitmap _slideBuffer;
        protected XFPanelScrollbar _scrollbar;
        protected XFPanelAlphabetBar _alphabar;
        protected bool hasBack;
        protected bool _scrollbarActive;
        protected Assembly localAssembly = Assembly.GetExecutingAssembly();
        protected Timer _animationTimer;
        protected bool _hasAnimation;

        public XFPanelBase()
        {
            InitializeComponent();
            customInit();
        }

        public XFPanelBase(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
            customInit();
        }

        public virtual void Reset()
        {
            tmKinetic.Enabled = false;
        }

        public void SetLeftPanel(XFPanelBase panel)
        {
            LeftPanel = panel;
            LeftPanel.RightPanel = this;
            Parent.Controls.Add(LeftPanel);
        }

        public void SetRightPanel(XFPanelBase panel)
        {
            RightPanel = panel;
            RightPanel.LeftPanel = this;
            Parent.Controls.Add(RightPanel);

        }

        public void FinishSlide()
        {
            if (LeftPanel != null && !hasBack && !LeftPanel.SavePanelAfterSlide)
            {
                Parent.Controls.Remove(LeftPanel);
                LeftPanel.Dispose();
                LeftPanel = null;
            }

            if (RightPanel != null && !RightPanel.SavePanelAfterSlide)
            {
                Parent.Controls.Remove(RightPanel);
                RightPanel.Dispose();
                RightPanel = null;
            }

            _curState = States.Idle;
            ((XFPanelContainer)Parent).SetMainPanel(this);
            Invalidate();
        }

        public void EnableSlide()
        {
            if (RightPanel != null)
                _curState = States.SlideLeft;
            else if (LeftPanel != null)
                _curState = States.SlideRight;

            tmSlide.Enabled = true;
            Invalidate();
        }

        public void SetHeadText(string text)
        {
            if (Parent != null)
            {
                if (Parent is XFPanelContainer && ((XFPanelContainer)Parent).GetHeaderText() != text)
                    ((XFPanelContainer)Parent).SetHeaderText(text);
            }
        }

        public virtual void ShowScrollbar(bool show)
        {
            _scrollbarActive = show;
            ScrollbarVisible = false;
        }

        public virtual void ShowAlphaBar(bool show)
        {
            AlphaBarVisible = show;
            if (show)
            {
                _alphabar = new XFPanelAlphabetBar();

                _alphabar.Height = Parent != null ? Parent.Height : Screen.PrimaryScreen.WorkingArea.Height;
                _alphabar.xOffset = Width - (ScrollbarVisible ? 5 : 0);

                _alphabar.Location = new Point(_alphabar.xOffset, 0);
                _alphabar.Parent = this;
            }
            else
                _alphabar = null;
        }

        public virtual void PanelPaint(Graphics g) { }

        public IXFModal ShowModal(XFModal type, string text)
        {
            if (_modal != null)
                _modal.Dispose();

            switch (type)
            {
                case XFModal.Bottom:
                    XFModalBottom modal = new XFModalBottom();
                    modal.Text = text;

                    _modal = modal;
                    break;
                case XFModal.Full:
                    break;
            }

            return _modal;
        }

        public void ShowModal(IXFModal modal)
        {
            if (_modal != null)
                _modal.Dispose();

            _modal = modal;
        }

        public void GetInputText()
        {
            if (Parent == null || !(Parent is XFPanelContainer))
                return;

            var cont = Parent as XFPanelContainer;
            cont.ShowTextInput();
            TextEntered = null;

            _curState = States.InsertText;
            Invalidate();
        }

        //  Events
        protected override void OnPaint(PaintEventArgs e)
        {
            if (!string.IsNullOrEmpty(PanelName))
                SetHeadText(PanelName);

            displayScroll();

            onPaintInit(e);

            if (_slideBuffer != null)
            {
                e.Graphics.DrawImage(_slideBuffer, slideX, 0);
                return;
            }
            Graphics g; //Offscreen graphics  
            if (_offBitmap == null) //Bitmap for doublebuffering  
            {
                _offBitmap = new Bitmap(Width, visibleHeight);
            }
            g = Graphics.FromImage(_offBitmap);

            if (State == States.InsertText)
            {
                XFControlUtils.DrawBlackout(g, new Rectangle(0, 0, Width, visibleHeight));
                e.Graphics.DrawImage(_offBitmap, 0, 0);
                return;
            }

            g.Clear(this.BackColor);

            PanelPaint(g);

            if (_alphabar != null)
                _alphabar.PainAlphaBar(g);

            if (_scrollbar != null)
            {
                _scrollbar.UpdatePanel(this);
                _scrollbar.PaintScroll(g);
            }

            if (_modal != null)
                setupModal(g);

            drawNotifications(g);

            e.Graphics.DrawImage(_offBitmap, 0, 0);

            if (_hasAnimation && (_animationTimer == null || !_animationTimer.Enabled))
                setupAnimation();
        }

        private void drawNotifications(Graphics g)
        {
            if (Parent != null && Parent is XFPanelContainer && ((XFPanelContainer)Parent).UpdateNotification != null)
            {
                if (!_updateSetup)
                {
                    _updateSetup = true;
                    ((XFPanelContainer)Parent).UpdateNotification.Updated += (o, e) =>
                    {
                        this.Invoke(new Action(() =>
                            {
                                Invalidate();
                            }));
                    };

                }

                var notifications = ((XFPanelContainer)Parent).UpdateNotification;
                if (notifications.Show)
                {
                    Rectangle rec = new Rectangle(0, 0, Width, notHeight);
                    using (SolidBrush b = new SolidBrush(Color.Red))
                        g.FillRectangle(b, rec);

                    int numb = notifications.GetNumber();
                    string display = numb + (numb == 1 ? " New Notification" : " New Notifications");

                    using (SolidBrush b = new SolidBrush(Color.Black))
                    using (Font f = new Font(FontFamily.GenericSansSerif, 9, FontStyle.Bold))
                    {
                        g.DrawString(display, f, b, 5, 5);
                        var mes = XFControlUtils.GetSizedStringMeasure(Width, "X", f);

                        float y = (notHeight - mes.Height) / 2;
                        float x = Width - y - mes.Width;
                        g.DrawString("X", f, b, x, y);
                    }
                }
            }
        }

        private void setupModal(Graphics g)
        {
            switch (_modal.State)
            {
                case XFModalState.Expanding:
                    _modal.CurHeight += (_modal.GetHeight() - _modal.CurHeight) / 3;

                    if (Math.Abs(_modal.CurHeight - _modal.GetHeight()) <= 3)
                    {
                        _modal.CurHeight = _modal.GetHeight();
                        _modal.State = XFModalState.Open;
                        _animationTimer.Enabled = false;
                        _animationTimer.Dispose();
                        _animationTimer = null;
                    }

                    setupAnimation();
                    break;

                case XFModalState.Collapsing:
                    _modal.CurHeight -= _modal.CurHeight / 3;

                    if (Math.Abs(_modal.CurHeight) <= 3)
                    {
                        _modal.CurHeight = 0;
                        _modal.State = XFModalState.Closed;
                        _modal.Dispose();
                        _modal = null;
                        _animationTimer.Enabled = false;
                        _animationTimer.Dispose();
                        _animationTimer = null;
                        return;
                    }

                    setupAnimation();
                    break;

                case XFModalState.Open:
                case XFModalState.Closed:
                    if (_animationTimer.Enabled)
                        _animationTimer.Enabled = false;
                    break;
            }

            if (_modal.Buffer == null)
            {
                _modal.Buffer = new Bitmap(Width, _modal.GetHeight());
                Graphics gm = Graphics.FromImage(_modal.Buffer);
                gm.Clear(BackColor);
                _modal.ItemPaint(gm, 0, 0);
            }
            g.DrawImage(_modal.Buffer, 0, visibleHeight - _modal.CurHeight);
        }

        private void onPaintInit(PaintEventArgs e)
        {
            if (Parent != null && Parent.BackColor != BackColor)
                Parent.BackColor = BackColor;

            if (visibleHeight < e.ClipRectangle.Height)
            {
                visibleHeight = e.ClipRectangle.Height;

                if (_offBitmap != null)
                    _offBitmap.Dispose();
                _offBitmap = null;
            }
        }

        private void setupAnimation()
        {
            if (_animationTimer == null)
            {
                _animationTimer = new Timer();
                _animationTimer.Tick += (o, et) =>
                {
                    Invalidate();
                };
            }
            _animationTimer.Interval = 30;
            _animationTimer.Enabled = true;
        }

        protected override void OnPaintBackground(PaintEventArgs e) { }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            switch (_curState)
            {
                case States.InsertText:
                    return;
                case States.Modal:
                    if (isInModal(new Point(e.X, e.Y)))
                    {
                        mouseDown = new Point(e.X, e.Y);
                        _modal.SendClick(e.X, (e.Y - (visibleHeight - _modal.GetHeight())));  //    convert the point to local coordinate system
                        break;
                    }
                    if (_modal != null && _modal.State == XFModalState.Open)
                        _modal.State = XFModalState.Collapsing;

                    _curState = States.Idle;
                    OnMouseDown(e);
                    break;
                case States.KineticScroll:
                case States.Idle:
                    if (Parent is XFPanelContainer && 
                        ((XFPanelContainer)Parent).UpdateNotification != null && 
                        ((XFPanelContainer)Parent).UpdateNotification.Show && 
                        e.Y <= notHeight)
                    {
                        _curState = States.UpdateClick;
                        return;
                    }

                    if (AlphaPanel(e.X))
                    {
                        _alphabar.PanelMouseDown(e);
                        _curState = States.AlphaPanel;
                        return;
                    }
                    _curState = States.Click;
                    mouseDown = new Point(e.X, e.Y);
                    tmKinetic.Enabled = false;
                    scrVelocity = 0;
                    scrLast = LocationY;

                    Invalidate();
                    break;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            switch (_curState)
            {
                case States.InsertText:
                    return;
                case States.Click:
                    if ((Attributes & PanelAttributes.Swipe) == PanelAttributes.Swipe)
                    {
                        if (moseMoveThreshold(e.Y))
                        {
                            _curState = States.Scroll;
                            offset = PointToScreen(new Point(0, e.Y)).Y - LocationY;
                        }
                    }
                    else
                    {
                        if (moseMoveThreshold(e.X, e.Y))
                        {
                            _curState = States.Scroll;
                            offset = PointToScreen(new Point(0, e.Y)).Y - LocationY;
                        }
                    }
                    break;
                case States.AlphaPanel:
                    if (AlphaPanel(e.X))
                        _alphabar.PanelMouseMove(e);
                    break;
                case States.Scroll:
                    if (_modal != null && _modal.State == XFModalState.Open)
                        _modal.State = XFModalState.Collapsing;

                    int newY = PointToScreen(new Point(0, e.Y)).Y - offset;

                    if (smallPanel() && (Attributes & PanelAttributes.ScrollPast) != PanelAttributes.ScrollPast)
                        LocationY = 0;
                    else if (scrollTooFar(newY) && (Attributes & PanelAttributes.ScrollPast) != PanelAttributes.ScrollPast)
                        setMainToBottom();
                    else if (scrollTooHigh(newY) && (Attributes & PanelAttributes.ScrollPast) != PanelAttributes.ScrollPast)
                        LocationY = 0;
                    else
                    {
                        LocationY = newY;

                        if (LocationY != scrLast)
                        {
                            scrVelocity = (LocationY - scrLast);
                            scrLast = LocationY;
                        }
                    }
                    break;
                case States.Modal:
                    if (moseMoveThreshold(e.X, e.Y) && _modal != null && _modal.State == XFModalState.Open)
                        _modal.ClearClick();
                    break;
            }

            Invalidate();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            switch (_curState)
            {
                case States.UpdateClick:
                    if (e.X > Width - notHeight)
                        ((XFPanelContainer)Parent).UpdateNotification.Show = false;

                    _curState = States.Idle;
                    break;
                case States.InsertText:
                    return;
                case States.Modal:
                    switch (_modal.GetClickResult())
                    {
                        case XFModalClickResult.Yes:
                        case XFModalClickResult.No:
                            if (_modal != null && _modal.State == XFModalState.Open)
                                _modal.State = XFModalState.Collapsing;
                            break;
                    }
                    break;
                case States.AlphaPanel:
                    _alphabar.PanelMouseUp(e);
                    _curState = States.Idle;
                    break;
                case States.Scroll:
                    time = 0.0;
                    _curState = States.Idle;

                    if (smallPanel())
                        LocationY = 0;
                    else if (scrollTooHigh(LocationY))
                        LocationY = 0;
                    else if (scrollTooFar(LocationY))
                        setMainToBottom();
                    else
                    {
                        if (Math.Abs(scrVelocity) > maxVelocity)
                            scrVelocity = scrVelocity > 0 ? maxVelocity : -maxVelocity;

                        scrVelocity = (int)(scrVelocity / 1.2);
                        tmKinetic.Enabled = true;
                        _curState = States.KineticScroll;
                    }
                    break;
                case States.SlideLeft:
                    RightPanel._curState = States.Busy;
                    tmSlide.Enabled = true;

                    if (Height < Screen.PrimaryScreen.WorkingArea.Height)
                        Height = Screen.PrimaryScreen.WorkingArea.Height;
                    break;
                case States.SlideRight:
                    LeftPanel._curState = States.Busy;
                    tmSlide.Enabled = true;

                    if (Height < Screen.PrimaryScreen.WorkingArea.Height)
                        Height = Screen.PrimaryScreen.WorkingArea.Height;
                    break;
            }

            Invalidate();
        }

        public void OnTextEntered(string text)
        {
            if (TextEntered != null)
            {
                TextEntered(this, new TextInputEventArgs(text));
            }

            TextEntered = null;
            _curState = States.Idle;
            Invalidate();
        }

        public void OnTextEnteredCancel()
        {
            TextEntered = null;
            _curState = States.Idle;
            Invalidate();
        }

        //  Queries
        private void displayScroll()
        {
            int height = Parent != null ? Parent.Height : Screen.PrimaryScreen.WorkingArea.Height;
            if ((_scrollbarActive && ScrollbarVisible && Height > height) || (!_scrollbarActive && !ScrollbarVisible))
                return;

            ScrollbarVisible = _scrollbarActive && Height > height;

            if (ScrollbarVisible)
            {
                _scrollbar = new XFPanelScrollbar();

                _scrollbar.Width = 5;
                _scrollbar.Height = height;
                _scrollbar.xOffset = Width;

                if (ScrollbarColor != null)
                    _scrollbar.BarColor = ScrollbarColor;

                Reset();

                if (_alphabar != null)
                {
                    _alphabar.xOffset -= 5;
                }
            }
            else if (_scrollbar != null)
            {
                _scrollbar.Dispose();
                _scrollbar = null;
            }
        }

        private bool AlphaPanel(int x)
        {
            return AlphaBarVisible && x > (_alphabar.xOffset - _alphabar.Width);
        }

        private bool moseMoveThreshold(int x, int y)
        {
            double dx = mouseDown.X - x;
            double dy = mouseDown.Y - y;

            dx *= dx;
            dy *= dy;

            double total = dx + dy;
            total = Math.Sqrt(total);

            return total > tolerance;
        }

        private bool moseMoveThreshold(int y)
        {
            double dy = Math.Abs(mouseDown.Y - y);

            return dy > tolerance;
        }

        private bool isInModal(Point point)
        {
            if (_modal == null || _modal.State != XFModalState.Open)
                return false;
            else if (point.Y > visibleHeight - _modal.GetHeight())
                return true;
            return false;
        }

        private void customInit()
        {
            visibleHeight = 0;
            LocationX = 0;
            LocationY = Location.Y;
            _curState = States.Idle;
            SavePanelAfterSlide = false;
            _hasAnimation = false;
        }

        private bool smallPanel()
        {
            if (Parent == null)
                return true;
            return Height < Parent.Height;
        }

        private bool scrollTooHigh(int y)
        {
            return y > 0;
        }

        private bool scrollTooFar(int y)
        {
            if (Parent == null)
                return true;
            return (Height + y) < Parent.Height;
        }

        private void setMainToBottom()
        {
            int x = Height - Parent.Height;
            LocationY = -x;
        }

        private void tmKinetic_Tick(object sender, EventArgs e)
        {
            double elaps = .2;
            double falloff = 4.0;
            int perc = 0;
            if ((perc = Height / 1000) > 0)
            {
                if (perc > 50)
                    falloff = 2.0;
                else
                    falloff = falloff - (falloff * ((double)perc / 100));
            }

            double dist = Math.Abs(scrVelocity) - (falloff * time);

            if (dist <= 0)
            {
                tmKinetic.Enabled = false;
                _curState = States.Idle;
                Invalidate();
                return;
            }

            if (scrVelocity > 0)
            {
                int newY = (int)(LocationY + dist);
                if (scrollTooHigh(newY))
                {
                    LocationY = 0;
                    tmKinetic.Enabled = false;
                    Invalidate();
                    return;
                }
                LocationY = newY;
            }
            else
            {
                int newY = (int)(LocationY - dist);
                if (scrollTooFar(newY))
                {
                    setMainToBottom();
                    tmKinetic.Enabled = false;
                    Invalidate();
                    return;
                }
                LocationY = newY;
            }
            Invalidate();
            time += elaps;
        }

        private void tmSlide_Tick(object sender, EventArgs e)
        {
            if (_slideBuffer == null)
                setupSlideBuffer();


            switch (_curState)
            {
                case States.SlideLeft:
                    int move = (Width + slideX) / 3;
                    if (RightPanel == null)
                    {
                        _curState = States.Idle;
                        tmSlide.Enabled = false;

                        if (_slideBuffer != null)
                            _slideBuffer.Dispose();
                        _slideBuffer = null;
                        return;
                    }

                    slideX -= move;

                    if (2 > move)
                    {
                        _curState = States.Idle;
                        tmSlide.Enabled = false;
                        RightPanel.LocationX = 0;
                        LocationX = Width;
                        RightPanel.FinishSlide();

                        if (_slideBuffer != null)
                            _slideBuffer.Dispose();
                        _slideBuffer = null;
                        return;
                    }
                    break;
                case States.SlideRight:
                    move = Math.Abs(slideX) / 3;
                    if (LeftPanel == null)
                    {
                        _curState = States.Idle;
                        tmSlide.Enabled = false;

                        if (_slideBuffer != null)
                            _slideBuffer.Dispose();
                        _slideBuffer = null;
                        return;
                    }

                    slideX += move;

                    if (2 > move)
                    {
                        _curState = States.Idle;
                        tmSlide.Enabled = false;
                        LeftPanel.LocationX = 0;
                        LocationX = Width;
                        LeftPanel.FinishSlide();

                        if (_slideBuffer != null)
                            _slideBuffer.Dispose();
                        _slideBuffer = null;
                        return;
                    }
                    break;
            }

            Invalidate();
        }

        private void setupSlideBuffer()
        {
            _slideBuffer = new Bitmap(Width * 2, Screen.PrimaryScreen.WorkingArea.Height);

            switch (_curState)
            {
                case States.SlideLeft:
                    if (RightPanel == null)
                        return;

                    using (Bitmap buffer = new Bitmap(Width, Screen.PrimaryScreen.WorkingArea.Height))
                    using (Graphics gb = Graphics.FromImage(buffer))
                    using (Graphics g = Graphics.FromImage(_slideBuffer))
                    {
                        g.Clear(BackColor);
                        gb.Clear(BackColor);

                        PanelPaint(g);
                        RightPanel.PanelPaint(gb);

                        g.DrawImage(buffer, Width, 0);
                    }

                    slideX = 0;
                    break;
                case States.SlideRight:
                    if (LeftPanel == null)
                        return;

                    using (Bitmap buffer = new Bitmap(Width, Screen.PrimaryScreen.WorkingArea.Height))
                    using (Graphics gb = Graphics.FromImage(buffer))
                    using (Graphics g = Graphics.FromImage(_slideBuffer))
                    {
                        g.Clear(BackColor);
                        gb.Clear(BackColor);

                        PanelPaint(gb);
                        LeftPanel.PanelPaint(g);

                        g.DrawImage(buffer, Width, 0);
                    }

                    slideX = -Width;
                    break;
            }

        }

        #region IDisposable Members

        new protected virtual void Dispose()
        {
            if (_offBitmap != null)
                _offBitmap.Dispose();

            if (_slideBuffer != null)
                _slideBuffer.Dispose();
        }

        #endregion
    }
}
